package com.naiterui.common.repository.core;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;

import com.naiterui.common.repository.exception.DataAccessOpException;
import com.naiterui.common.repository.exception.DataAccessSafeException;

public class BaseMongoRepositoryImpl<T, ID extends Serializable> extends SimpleMongoRepository<T, ID> implements BaseMongoRepository<T, ID> {

    private final MongoOperations mongoOperations;
    private final MongoEntityInformation<T, ID> entityInformation;

    public BaseMongoRepositoryImpl(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {
        super(metadata, mongoOperations);
        this.entityInformation = metadata;
        this.mongoOperations = mongoOperations;
    }

    /**
     * 获取对象
     *
     * @param id
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2017年11月18日
     * @since 1.0.0
     */
    @Override
    public T get(ID id) {
        Optional<T> optional = this.findById(id);
        return optional.orElse(null);
    }

    /**
     * 保存对象
     *
     * @param entity
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2017年11月18日
     * @see org.springframework.data.mongodb.repository.support.SimpleMongoRepository#save(S)
     * @since 1.0.0
     */
    @Override
    public <S extends T> S save(S entity) {
        if (!this.entityInformation.isNew(entity)) {
            throw new DataAccessOpException("persistent object detected !");
        }
        this.mongoOperations.insert(entity, this.entityInformation.getCollectionName());
        return entity;
    }

    /**
     * 更新对象
     *
     * @param entity
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2017年11月18日
     * @see BaseOperations#update(java.lang.Object)
     * @since 1.0.0
     */
    @Override
    public <S extends T> S update(S entity) {
        if (this.entityInformation.isNew(entity)) {
            throw new DataAccessOpException("detecting non persistent object ! !");
        }
        this.mongoOperations.save(entity, this.entityInformation.getCollectionName());
        return entity;
    }

    /**
     * 批量保存
     *
     * @param entities
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2017年11月18日
     * @since 1.0.0
     */
    @Override
    public <S extends T> List<S> save(Iterable<S> entities) {
        return this.saveOrUpdate(entities, false);
    }

    /**
     * 批量更新
     *
     * @param entities
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2017年11月18日
     * @see BaseOperations#update(java.lang.Iterable)
     * @since 1.0.0
     */
    @Override
    public <S extends T> List<S> update(Iterable<S> entities) {
        return this.saveOrUpdate(entities, true);
    }

    /**
     * 删除对象
     *
     * @param s
     *
     * @Author zhouyanxin
     * @Date 2018年5月2日
     * @see BaseOperations#remove(java.lang.Object)
     * @since 1.0.0
     */
    @Override
    public <S extends T> void remove(S s) {
        super.delete(s);
    }

    /**
     * 根据ID删除对象
     *
     * @param id
     *
     * @Author zhouyanxin
     * @Date 2018年5月2日
     * @since 1.0.0
     */
    @Override
    public void removeById(ID id) {
        super.deleteById(id);
    }

    /**
     * 批量删除
     *
     * @param entities
     *
     * @Author zhouyanxin
     * @Date 2017年11月18日
     * @since 1.0.0
     */
    @Override
    public void remove(List<T> entities) {
        super.deleteAll(entities);
    }

    /**
     * 禁用方法
     *
     * @Author zhouyanxin
     * @Date 2017年11月18日
     * @see org.springframework.data.mongodb.repository.support.SimpleMongoRepository#deleteAll()
     * @since 1.0.0
     */
    @Override
    public void deleteAll() {
        throw new DataAccessSafeException("not support operation, This operation is risky !");
    }

    /**
     * 禁用方法
     *
     * @param entity
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2017年11月18日
     * @see org.springframework.data.mongodb.repository.support.SimpleMongoRepository#insert(S)
     * @since 1.0.0
     */
    @Override
    public <S extends T> S insert(S entity) {
        throw new DataAccessSafeException("not support operation, switch to method [save] !");
    }

    /**
     * 禁用方法
     *
     * @param entities
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2017年11月18日
     * @see org.springframework.data.mongodb.repository.support.SimpleMongoRepository#insert(java.lang.Iterable)
     * @since 1.0.0
     */
    @Override
    public <S extends T> List<S> insert(Iterable<S> entities) {
        throw new DataAccessSafeException("not support operation, switch to batch method [save] !");
    }

    /**
     * 批量保存或更新操作
     * 根据操作类型进行检查
     * 批量保存要求全部对象都必须是游离状态
     * 批量更新要求全部对象都不许是持久状态
     * BaseMongoRepositoryImpl.saveOrUpdate()
     *
     * @param entities
     * @param update   是否是更新操作 true：更新 false：保存
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2017年11月18日
     * @since 1.0.0
     */
    private <S extends T> List<S> saveOrUpdate(Iterable<S> entities, boolean update) {

        List<S> result = new ArrayList<>();

        if (entities == null) {
            return result;
        }

        for (S entity : entities) {
            if (this.entityInformation.isNew(entity) == update) {
                throw new DataAccessOpException("persistent object detected !");
            }
            if (!update) {
                result.add(entity);
            }
        }

        if (update) {
            for (S entity : entities) {
                result.add(this.update(entity));
            }
        } else {
            this.mongoOperations.insertAll(result);
        }

        return result;
    }

    /**
     * @param query
     *
     * @return
     */
    @Override
    public List<T> find(Query query) {
        return this.mongoOperations.find(query, this.entityInformation.getJavaType());
    }

    /**
     * @param query
     * @param pageable
     *
     * @return
     */
    @Override
    public Page<T> find(Query query, Pageable pageable) {

        long count = this.count(query);
        int skipNum = (pageable.getPageSize() - 1) * pageable.getPageSize();
        query = query.skip(skipNum).limit(pageable.getPageSize()).with(pageable.getSort());
        List<T> list = this.find(query.with(pageable));

        return new PageImpl<>(list, pageable, count);

    }

    /**
     * @param query
     *
     * @return
     */
    @Override
    public long count(Query query) {
        return this.mongoOperations.count(query, this.entityInformation.getJavaType());
    }
}
